﻿using System;
using System.Collections.Generic;
using System.Linq;
using Equation.Enterprise.Utilities.DesignByContract;
using log4net;
using log4net.Appender;
using log4net.Core;
using log4net.Filter;
using log4net.Layout;
using log4net.Repository.Hierarchy;

namespace Equation.Enterprise.Utilities.Log
{
    /// <summary>
    /// Produce correct Logger bases on LoggerStorage
    /// </summary>
    public class LoggerFactory
    {
        public const string DefaultPattern = "[%d]: %m%n";
        
        public const string DefaultLogFileExtension = ".log";
        
        public const string DefaultInfoLogFile = "info";
        public const string DefaultWarningLogFile = "warning";
        public const string DefaultDebugLogFile = "debug";
        public const string DefaultErrorLogFile = "error";

        private static readonly IList<LogStorage> LogStorages = new List<LogStorage>();
        private static readonly Hierarchy Hierarchy = (Hierarchy) LogManager.GetRepository();

        public static void Register(string logKey, string fileName, LogLevel logLevel=LogLevel.Info, bool isDefault = true)
        {
            Check.Require(!string.IsNullOrEmpty(logKey) && !string.IsNullOrWhiteSpace(logKey));

            var logStorage = LogStorages.Where<LogStorage>(storage => storage.Key == logKey).FirstOrDefault();
            if (logStorage == null)
            {
                LogStorages.Add(new LogStorage
                                    {
                                        Key = logKey,
                                        Default = isDefault,
                                        LogLevel = logLevel
                                    });
                Level level = GetLogLevel(logLevel);
                IAppender appender = CreateRollingAppender(logKey, fileName, level);
                Register(appender, logKey);
            }
            else
            {
                throw new ApplicationException("This " + logKey + " was registered. Please use different Log Key");
            }
        }

        internal static ILog FirstOrDefault(string logKey, LogLevel logLevel)
        {
            if (string.IsNullOrEmpty(logKey) || string.IsNullOrWhiteSpace(logKey))
                logKey = GetDefaultLogKey(logLevel);

            var logStorage =
                LogStorages.Where(storage => storage.Key == logKey && storage.LogLevel == logLevel).FirstOrDefault();

            if (logStorage == null)
            {
                //Try again with default log storage
                logStorage =
                    LogStorages.Where(storage => storage.Default && storage.LogLevel == logLevel).FirstOrDefault();

                if (logStorage == null)
                {
                    string fileName = logKey + DefaultLogFileExtension;
                    Register(logKey, fileName, logLevel);
                    return LogManager.GetLogger(logKey);
                }
            }

            return LogManager.GetLogger(logKey);
        }

        public static void Shutdown()
        {
            LogStorages.Clear();
            LogManager.Shutdown();
        }

        #region Private Methods

        private static string GetDefaultLogKey(LogLevel logLevel)
        {
            switch(logLevel)
            {
                case LogLevel.Warning:
                    return DefaultWarningLogFile;
                case LogLevel.Debug:
                    return DefaultDebugLogFile;
                case LogLevel.Info:
                    return DefaultInfoLogFile;
                case LogLevel.Error:
                    return DefaultErrorLogFile;
                default:
                    return DefaultInfoLogFile;
            }
        }

        private static IAppender CreateRollingAppender(string logKey, string fileName, Level logLevel)
        {
            return CreateRollingAppender(logKey, fileName, DefaultPattern, logLevel);
        }

        private static IAppender CreateRollingAppender(string logKey, string fileName, string pattern, Level logLevel)
        {
            Check.Require(logKey != null && fileName !=null && pattern!=null);

            PatternLayout patternLayout = CreatePatternLayout(pattern);
            var roller = new RollingFileAppender();
            roller.Name = logKey;
            roller.Layout = patternLayout;
            roller.AppendToFile = true;
            roller.RollingStyle = RollingFileAppender.RollingMode.Composite;
            roller.MaxSizeRollBackups = 100;
            roller.MaximumFileSize = "10KB";
            roller.StaticLogFileName = true;
            roller.File = fileName;
            roller.LockingModel = new FileAppender.MinimalLock();

            var filter = new LevelRangeFilter
                             {
                                 LevelMax = logLevel,
                                 LevelMin = logLevel
                             };
            roller.AddFilter(filter);
            roller.Threshold = logLevel;
            roller.ActivateOptions();

            return roller;
        }

        private static PatternLayout CreatePatternLayout(string pattern)
        {
            Check.Require(pattern != null);

            var patternLayout = new PatternLayout {ConversionPattern = pattern};
            patternLayout.ActivateOptions();

            return patternLayout;
        }

        private static void Register(IAppender appender, string logKey)
        {
            var hierarchy = (Hierarchy)LogManager.GetAllRepositories().Where(x => x.Name == logKey).FirstOrDefault();

            if (hierarchy == null)
                hierarchy = (Hierarchy)LogManager.CreateRepository(logKey);

            Hierarchy.Root.AddAppender(appender);
            Hierarchy.Configured = true;
        }

        private static Level GetLogLevel(LogLevel logLevel)
        {
            switch (logLevel)
            {
                case LogLevel.Warning:
                    return Level.Warn;
                case LogLevel.Debug:
                    return Level.Debug;
                case LogLevel.Error:
                    return Level.Error;
                default:
                    return Level.Info;
            }
        }

        #endregion
    }
}
